﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Text.RegularExpressions;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Data.SQLite;
//using MySql.Data.MySqlClient;

using Urd.Common;
using Urd.Packet;

namespace Urd.Common
{
    public sealed class async_cached_manager:manager
    {

        struct clientinfo 
        {
            public TcpClient client;
            public NetworkStream stream;
            public byte[] buffer;
            public packet p;
            public bool statusread;
            public bool statuswrite;

            public clientinfo(TcpClient c,NetworkStream ns ,byte[] b,packet p) 
            {
                client = c;
                stream = ns;
                buffer = b;
                this.p = p;
                statusread = statuswrite = false;
            }
        }

       // public delegate packet Process(cache c,packet p = null);

        //#region fields
        //string lasterror;
        ////logic
        System.Globalization.CultureInfo customCulture;

        //string managername, tablename;
        //public string Name { get { return managername; } }
        //public string Table { get { return tablename; } }
        //string tableshema;
        //List<string> columnnames;
        protected worker subordinate;

        //bool working, listen;
        //public bool Working { get { return working; } }
        //Thread /*worker,*/ netthread;//, thread_process_client;//thread_write, thread_read;
        //System.Threading.Timer timer, timer2;

        //cache sqlcache;
        //public cache Cache { get { return sqlcache; } }
        ////logic

        ////net
        //protected IPAddress address;
        //public string Address { get { return address.ToString(); } }

        //protected int port;
        //public string Port { get { return port.ToString(); } }

        ////List<clientinfo> clients;
        ////List<NetworkStream> streams;
        //int pps;
        int maxpps;
        //public string PacketPerSecond { get { return (maxpps*0.2).ToString(); ; } }
        //TcpListener listener;
        bool serial_or_parralel;
        ////-net

        ////
        //protected Opcode opcode_to_accept;
        //public string Code { get { return opcode_to_accept.ToString(); } }

        private static ManualResetEvent writeDone =
            new ManualResetEvent(false);
        private static ManualResetEvent readDone =
            new ManualResetEvent(false);
        private static ManualResetEvent stopDone =
            new ManualResetEvent(false);


        //List<packet> asq,snt;

        //int asquired_packets_count;
        //bool flag, lastflag;
        //packet cached_packet;
        

        //#endregion
        public void Dispose() 
        {
            sqlcache.Dispose();
            timer.Dispose();
            timer2.Dispose();
        }
        public void stop()
        {
            working = false;
            stop_cache();
            stop_network();


            columnnames = null;


        }
        public void start()
        {
            

            pps = 0;
            columnnames = new List<string>();

            ///
            start_network();

            ///
            start_cache();

            timer2 = new Timer((object o) => { maxpps = pps; pps = 0; }, null, 5000, 5000);
            working = true;
            //register in manager list here
        }

        public async_cached_manager(managerinfo info, worker Worker, bool serial_true_parralel_false=true, string SQLshema = "")
        {
            customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
            customCulture.NumberFormat.NumberDecimalSeparator = ".";
            opcode_to_accept = info.able_to_accept;
            managername = info.managername;
            dbfilename = info.filename;
            dbtablename = info.tablename;

            address = info.address;
            port = info.port;
            subordinate = Worker;
            
            serial_or_parralel = serial_true_parralel_false;
           
        }
      

        #region network funk

        private void start_network()
        {
            try
            {
               // clients = new List<clientinfo>();
                //streams = new List<NetworkStream>();
                listener = new TcpListener(address, port);
                listener.Start();
                listen = true;
                netthread = new Thread(new ThreadStart(acceptClients));
                netthread.Name = managername + " network listen thread";
                netthread.Start();
                
                

            }
            catch (Exception e) { debug.Log("[" + managername + "][network]:" + e.Source + " " + e.Message, debug.loglevel.Errors); }
        }

        private void stop_network()
        {
            if (listener != null)
            {
                listen = false;
                netthread.Abort();
                listener.Stop();
                listener = null;
            }
        }
       
        void acceptClients()
        {

            while (listen)
            {
             
                if (listener.Pending())
                {
                    
                    listener.BeginAcceptTcpClient(accept_callback, listener);

                }
                Thread.Sleep(1);
            }
        }

        void accept_callback(IAsyncResult res)
        {
            TcpListener l = (TcpListener)res.AsyncState;
            if (serial_or_parralel)
            {
                
                TcpClient c = l.EndAcceptTcpClient(res);
                try
                {
                    if (c.Connected)
                    {
                        NetworkStream ns = c.GetStream();

                        byte[] buf = new byte[1024];
                        clientinfo cinfo = new clientinfo(c, ns, buf, null);

                        readDone.Reset();
                        ns.BeginRead(buf, 0, c.Available, read_callback, cinfo);
                    }
                }
                catch (Exception e) 
                {
                    //lasterror = e.Message;
                    c.Close();
                }
            }
            else 
            {
                
                TcpClient c = l.EndAcceptTcpClient(res);
                try
                {
                    if (c.Connected)
                    {
                        //thrread sync
                        writeDone.Reset();
                        readDone.Reset();

                        NetworkStream ns = c.GetStream();
                        byte[] buf = new byte[8096];
                        clientinfo cinfo = new clientinfo(c, ns, buf, null);

                        //читаем что нам пришло
                        cinfo.stream.BeginRead(cinfo.buffer, 0, cinfo.buffer.Length, p_read_callback, cinfo);

                        //пишем дамп
                        packet dump = subordinate.process(sqlcache, new packet(subordinate.opcode, Command.Get));
                        byte[] bytes = dump.Bytes;
                        int b_count = bytes.Length;
                        cinfo.stream.BeginWrite(bytes, 0, b_count, p_write_callback, cinfo);


                        writeDone.WaitOne();
                        readDone.WaitOne();
                        cinfo.stream.Close();
                        cinfo.client.Close();
                    }
                }
                catch (Exception e)
                {
                    //lasterror = e.Message;
                    c.Close();
                }
                finally { pps++; }
             
            }

        }

        

         void p_read_callback(IAsyncResult res) 
         { 
             clientinfo cinfo = (clientinfo)res.AsyncState;
             try
             {
                 int count = cinfo.stream.EndRead(res);
                 if (count > 0)
                 {
                     
                         packet p = new packet(cinfo.buffer);
                         subordinate.process(sqlcache, p);
                    
                 }
             }
             catch (Exception e) 
             {
                 //lasterror = e.Message; 
             }

             finally 
             { 
                 readDone.Set();
                 cinfo.statusread = true;
                // while (!(cinfo.statusread && cinfo.statuswrite)) Thread.Sleep(4);
             }
         }

         void p_write_callback(IAsyncResult res)
         {
             clientinfo cinfo = (clientinfo)res.AsyncState;

             try
             {                 
                 cinfo.stream.EndWrite(res);
             }
             catch (Exception e)
             {
                // lasterror = e.Message; 
             }
             finally 
             {
                 writeDone.Set();
                 cinfo.statuswrite = true;
                // while (!(cinfo.statusread && cinfo.statuswrite)) Thread.Sleep(4);
             }
         }

        void read_callback(IAsyncResult res) 
        {
            clientinfo cinfo = (clientinfo)res.AsyncState;
            try
            {
                int count = cinfo.stream.EndRead(res);
            readDone.Set();
           
                if (cinfo.client.Connected)
                {
                    cinfo.p = new packet(cinfo.buffer);
                    packet p = subordinate.process(sqlcache, cinfo.p);
                    cinfo.p = p;
                    if (cinfo.p.command != Command.None)
                    {
                        byte[] buf = cinfo.p.Bytes;
                        writeDone.Reset();
                        cinfo.stream.BeginWrite(buf, 0, buf.Length, write_callback, cinfo);
                       // writeDone.WaitOne();
                    }
                   
                }
            }
            catch (Exception e) 
            {
                //lasterror = e.Message;
            }
           {
           //    flag = lastflag;
           //    cached_packet = new packet(Opcode.Error, Command.New);
           //    cached_packet.Push(e.Message);
              
            }
            
            //readDone.Set();
        }


        void write_callback(IAsyncResult res)
        {
            clientinfo cinfo = (clientinfo)res.AsyncState;
            cinfo.stream.EndWrite(res);
            pps++;
          // writeDone.Set();
            
        }
#region shit
        //void thread_read_work() 
        //{
        //    while (true)
        //    {
        //        cleanupDone.WaitOne();
        //        readDone.Reset();
                
        //        for (int i = 0; i < clients.Count; i++)
        //        {
        //            try
        //            {
        //                if (clients[i].client.Connected)
        //                {
        //                    NetworkStream ns = clients[i].client.GetStream();

        //                    if (ns.DataAvailable)
        //                    {
        //                        // lock (sqlcache)
        //                        //{
        //                        // NetworkStream ns = clients[i].stream;
        //                        byte[] buffer = new byte[127];
        //                        ns.Read(buffer, 0, buffer.Length);
        //                        packet p = new packet(buffer);
        //                        clientinfo c = new clientinfo();
        //                        packet pp = process(p, sqlcache);

        //                        c.buffer = buffer;
        //                        c.client = clients[i].client;
        //                        c.tosent = pp;
        //                        c.asq = p;
        //                        c.stream = ns;
        //                        clients[i] = c;
                                

        //                        // }
        //                    }
        //                    ns.Close();
        //                    clients[i].client.Close();
        //                }
        //            }
        //            catch (Exception e)
        //            {
        //                debug.Log(e.Message, debug.loglevel.Warnings);

        //            }
        //            finally 
        //            {
        //                //clients[i].stream.Close();
        //               // if(!clients[i].client.Connected) clients[i].client.Close();
        //                //clients[i] = new clientinfo(null, null, null, null);
        //            }
        //        }
        //        readDone.Set();
        //        Thread.Sleep(4);
        //    }
            
        //}
        //void thread_write_work() 
        //{
        //    while (true) 
        //    {
        //        cleanupDone.WaitOne();
        //        readDone.WaitOne();
        //        for (int i = 0; i < clients.Count; i++)
        //        {
        //            try
        //            {

        //                if (clients[i].client.Connected)
        //                {
        //                    if (clients[i].tosent != null)
        //                    {
                                
        //                        NetworkStream ns = clients[i].client.GetStream();
        //                        if (ns.CanWrite)
        //                        {
        //                            //ns.BeginWrite(clients[i].tosent.Bytes, 0, 0,write_callback,ns);
        //                            byte[] buf = clients[i].tosent.Bytes;
        //                            foreach (byte b in buf)
        //                            {
        //                                ns.WriteByte(b);
        //                            }
        //                        }
        //                        //snt.Add(clients[i].currentpacket);
        //                        //Thread.Sleep(200);
                               
        //                    }

        //                }
                        

                        
        //            }
        //            catch (Exception e)
        //            {
        //                debug.Log(e.Message, debug.loglevel.Warnings);

        //            }
        //            finally 
        //            {
        //                //clients[i].stream.Close();
        //                if (!clients[i].client.Connected) clients[i].client.Close();
        //                //clients[i] = new clientinfo(null, null, null, null);
        //            }
        //            Thread.Sleep(3);
                    
        //        }


        //        Thread.Sleep(40);
        //    }
        //}
   
#endregion
        #endregion

        #region cache funk
        public void dump()
        {
            if (!System.IO.Directory.Exists(server.config.tablesfolder)) System.IO.Directory.CreateDirectory(server.config.tablesfolder);
            sqlcache.DumpTable(server.config.tablesfolder + this.dbfilename + ".db");
        }

        private void start_cache()
            //(bool take_from_mysql)
        {
            if (this.dbtablename == "") this.dbtablename = managername;
            if (this.dbfilename == "") this.dbfilename = managername;
            sqlcache = new cache();
           // if (take_from_mysql)
           // {
           //     prepare_shema();
           //     prepare_cache(true);
           // }
           // else
           // {
                string filename = server.config.tablesfolder + this.dbfilename + ".db";
                SQLiteConnection con = new SQLiteConnection("Data Source=" + filename + ";Version=3;New=True;");
                con.Open();
                con.BackupDatabase(sqlcache.Connection, "main", "main", -1, null, -1);
                con.Close();
          //  }
            timer = new System.Threading.Timer(new TimerCallback(sync), null, 4000, 4000);
            //cached_packet = process(sqlcache, null);
        }
        private void stop_cache()
        {
            if (sqlcache != null)
            {
                timer.Dispose();
                sqlcache.close();
                sqlcache = null;
            }
        }
        //private void prepare_shema()
        //{

        //    string database_schema = "";

        //    object[] schema_unformatted
        //        = mysql.Sql("show create table " + tablename, true);

        //    database_schema = schema_unformatted[1] as string;

        //    string pattern = @"`\w+`";

        //    MatchCollection matches = Regex.Matches(database_schema, pattern);

        //    foreach (Match m in matches)
        //    {
        //        string val = m.Value.Replace("`", "");
        //        if (!columnnames.Contains(val)) columnnames.Add(val);
        //    }

        //    int end = database_schema.LastIndexOf(')') + 2;

        //    database_schema = database_schema.Remove(end);
        //    database_schema = database_schema.Replace("unsigned", " ");
        //    database_schema = database_schema.Replace("AUTO_INCREMENT", "");

        //    tableshema = database_schema;


        //}

        //private void prepare_cache(bool backup)
        //{
        //    try
        //    {
        //        System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;
        //        //here we can backup create table info
        //        if (backup)
        //        {
        //            if (!System.IO.Directory.Exists(server.config.shemasfolder)) System.IO.Directory.CreateDirectory(server.config.shemasfolder);
        //            System.IO.File.WriteAllText(server.config.shemasfolder + dbfilename+"_"+dbtablename + ".sql", tableshema);
        //        }
        //        sqlcache.Sql(tableshema, false);

        //        int max = 0;
        //        object ret = mysql.Sql("select max(id) from " + tablename, true)[0];
        //        if (ret.GetType() != typeof(System.DBNull))
        //        {
        //            max = (int)ret;
        //            for (int i = 0; i <= max; i++)
        //            {
        //                string values = "";
        //                object[] val = mysql.Sql("select * from " + tablename + " where id=" + i.ToString(), true);
        //                int l = val.Length;
        //                for (int c = 0; c < l; c++)
        //                {
        //                    if (val[c] != null)
        //                    {
        //                        string temp = val[c].ToString();
        //                        if (temp == "") temp = "null";
        //                        int ii;
        //                        float ff;
        //                        if (!Int32.TryParse(temp, out ii) && !Single.TryParse(temp, out ff) && temp != "null") temp = "'" + temp + "'";
        //                        values += temp;
        //                        if (c < l - 1) values += ",";
        //                    }
        //                }
        //                if (values != "")
        //                    sqlcache.Sql("insert into " + tablename + " values(" + values + ")", false);
        //            }
        //        }
        //    }
        //  //  catch (MySqlException e) { Urd.Common.debug.Log("[prepare_cache][cache][mysql]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
        //    catch (SQLiteException e) { Urd.Common.debug.Log("[prepare_cache][cache][sqlite]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
        //    catch (Exception e) { Urd.Common.debug.Log("[prepare_cache][cache][system]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }


        //}
        private void sync(object o)
        {
            //bool state = (bool)o;
            //if (state)
            //{
            //    try
            //    {
            //        System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;

            //        string query = "";
            //        long max = 0;
            //        object[] temp = sqlcache.Sql("select max(id) from " + this.dbtablename, true);

            //        if (temp[0].GetType() != typeof(System.DBNull))
            //        {
            //            max = (long)temp[0];
            //            long min = (long)sqlcache.Sql("select max(id) from " + this.dbtablename, true)[0];
            //            for (long i = min; i <= max; i++)
            //            {
            //                string askquery = "";

            //                for (int cc = 1; cc < columnnames.Count; cc++)
            //                {
            //                    askquery += "'" + columnnames[cc] + "'";

            //                    if (cc < columnnames.Count - 1) askquery += ",";
            //                }
            //                object[] val = sqlcache.Sql("select * from " + tablename + " where id=" + i.ToString(), true);
            //                int l = val.Length;
            //                if (l == columnnames.Count - 1)
            //                {
            //                    foreach (object ob in val) { if (ob == null) return; }

            //                    query += "update ";
            //                    query += columnnames[0];
            //                    query += " set ";
            //                    for (int cc = 0; cc < val.Length; cc++)
            //                    {
            //                        if (!(val[cc].GetType() == typeof(DBNull)))
            //                        {
            //                            query += columnnames[cc + 1] + " = '" + val[cc] + "'";
            //                            if (cc < val.Length - 1) query += " , ";
            //                        }
            //                    }



            //                    query += " where id=" + val[0].ToString() + ";";
            //                }
            //                //cache.Sql("insert into pos values(" + values + ")", false);
            //                if (query != "") mysql.Sql(query, false);
            //            }

            //        }

            //    }
            // //   catch (MySqlException e) { Urd.Common.debug.Log("[cache][mysql]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
            //    catch (SQLiteException e) { Urd.Common.debug.Log("[cache][sqlite]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
            //    catch (Exception e) { Urd.Common.debug.Log("[cache][system]:" + e.Source + " " + e.Message, Urd.Common.debug.loglevel.Errors); }
            //}
            //else
            dump();
        }

        #endregion
    }
}


